#include "sd_hashtable.h"
#include "sd_memory.h"
#include "sd_log.h"

sd_Ret sd_HashtableCreate(sd_Hashtable** table, 
						  sd_HashFunc hash_func,
						  sd_HashKeyCompare key_cmp, 
						  size_t hashsize, 
						  size_t bucketNodeSize, 
						  size_t keysize, 
						  size_t elemsize,
						  struct sd_MemoryAllocator* ma)
{
	sd_Ret ret;
	uint32 i;
	if (ma == NULL)
		ma = sd_GetDefaultMemoryAllocator();

	*table = (sd_Hashtable*)ma->allocate(sizeof(sd_Hashtable)+(hashsize-1)*sizeof(sd_Blist));
	if (*table == NULL)
	{
		sd_log_err("Cannot allocate enough memory for the creation of the hash table!\n");
		SD_ASSERT(false);
		return SD_RET_ALLOCATE_MEMEORY;
	}
	(*table)->ma = ma;
	(*table)->keysize = keysize;
	(*table)->elemsize = elemsize;
	(*table)->total_count = 0;
	(*table)->hash_func = hash_func;
	(*table)->key_cmp = key_cmp;
	(*table)->hashsize = hashsize;
	for (i=0;i<hashsize; i++)
	{
		/* Notice: in this block list, each element should contain the both key and data part */
		ret = sd_BlistCreate(&(*table)->buckets[i], keysize+elemsize, bucketNodeSize, ma);
		SD_ON_FAILED_RETURN(ret);
	}
	return SD_RET_OK;
}

sd_Ret sd_HashtableFree(sd_Hashtable* table)
{
	uint32 i;
	struct sd_MemoryAllocator* ma = table->ma;
	for (i=0;i<table->hashsize;i++)
	{
		sd_BlistFree(&table->buckets[i]);
	}
	ma->deallocate(table);
	return SD_RET_OK;
}

sd_Ret sd_HashtableInsert(sd_Hashtable* table, const void* key, const void* elem)
{
	sd_Ret ret;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;
	
	ret = sd_BlistInsertTogether(&table->buckets[hashindex], key, table->keysize, elem);
	SD_ON_FAILED_RETURN(ret);

	table->total_count++;
	return SD_RET_OK;
}

sd_Ret sd_HashtableLookup(sd_Hashtable* table, const void* key, void* elem)
{
	sd_Key k;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;
	sd_HashKeyCompare key_cmp = table->key_cmp;
	sd_BlistIterator iter;

	if (table->total_count == 0)
		return SD_RET_KEY_NOTFOUND;
	
	sd_BlistBegin(&table->buckets[hashindex], &iter);
	while(sd_BlistNext(&iter) != SD_RET_FALSE)
	{
		sd_BlistPartialGet(&iter, &k, 0, table->keysize);
		if (key_cmp(key, &k) == 0)
		{
			if (elem != NULL)
			{
				/* The content field's offset is started after the 'Key' field */
				sd_BlistPartialGet(&iter, elem, (int)table->keysize, table->elemsize);
			}
			return SD_RET_OK;
		}
	}
	return SD_RET_KEY_NOTFOUND;
}

sd_Ret sd_HashtableUpdate(sd_Hashtable* table, const void* key, const void* elem)
{
	sd_Key k;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;
	sd_HashKeyCompare key_cmp = table->key_cmp;
	sd_BlistIterator iter;

	if (table->total_count == 0)
		return SD_RET_KEY_NOTFOUND;
	
	sd_BlistBegin(&table->buckets[hashindex], &iter);
	while(sd_BlistNext(&iter) != SD_RET_FALSE)
	{
		sd_BlistPartialGet(&iter, &k, 0, table->keysize);
		if (key_cmp(key, &k) == 0)
		{
			sd_BlistUpdateTogether(&iter, key, table->keysize, elem);
			return SD_RET_OK;
		}
	}
	return SD_RET_KEY_NOTFOUND;
}

sd_Ret sd_HashtableDelete(sd_Hashtable* table, const void* key)
{
	sd_Key k;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;
	sd_HashKeyCompare key_cmp = table->key_cmp;
	sd_BlistIterator iter;

	if (table->total_count == 0)
		return SD_RET_KEY_NOTFOUND;
	
	sd_BlistBegin(&table->buckets[hashindex], &iter);
	while(sd_BlistNext(&iter) != SD_RET_FALSE)
	{
		sd_BlistPartialGet(&iter, &k, 0, table->keysize);
		if (key_cmp(key, &k) == 0)
		{
			sd_BlistRemove(&iter);
			table->total_count--;
			return SD_RET_OK;
		}
	}
	return SD_RET_KEY_NOTFOUND;
}

sd_Ret sd_HashtableClear(sd_Hashtable* table)
{
	uint32 i;
	for (i=0;i<table->hashsize; i++)
	{
		sd_BlistClear(&table->buckets[i]);
	}
	table->total_count = 0;
	return SD_RET_OK;
}

sd_Ret sd_HashtableBegin(sd_Hashtable* table, sd_HashtableIterator* iter)
{
	iter->hashtable = table;
	iter->bucketIndex = -1;
	return SD_RET_OK;
}

sd_Ret sd_HashtableNext(sd_HashtableIterator* iter)
{
	sd_Ret ret;
	if (iter->bucketIndex == -1)
	{
		iter->bucketIndex = 0;
		if (iter->hashtable->hashsize == 0)
			return SD_RET_FALSE;
		sd_BlistBegin(&iter->hashtable->buckets[0], &iter->bucketIter);
	}
	ret = sd_BlistNext(&iter->bucketIter);
	while (ret == SD_RET_FALSE)
	{
		if ((size_t)iter->bucketIndex == iter->hashtable->hashsize -1)
		{
			/* No more list to iterate*/
			return SD_RET_FALSE;
		}
		else
		{
			/* Jump to the next list */
			iter->bucketIndex++;
			sd_BlistBegin(&iter->hashtable->buckets[iter->bucketIndex], &iter->bucketIter);
			ret = sd_BlistNext(&iter->bucketIter);
		}
	}
	return ret;
}

sd_Ret sd_HashtableGet(sd_HashtableIterator* iter, void* elem)
{
	return sd_BlistPartialGet(&iter->bucketIter, elem, (int)iter->hashtable->keysize, iter->hashtable->elemsize);
}

sd_Ret sd_HashtableGetKey(sd_HashtableIterator* iter, void* elem)
{
	return sd_BlistPartialGet(&iter->bucketIter, elem, 0, iter->hashtable->keysize);
}

uint32 sd_HashFunc_uint32(const void* key)
{
	return *(uint32*)key;
}

int sd_HashKeyCompare_uint32(const void* k1, const void* k2)
{
	uint32 ik1 = *(uint32*)k1;
	uint32 ik2 = *(uint32*)k2;
	if (ik1 < ik2)
		return -1;
	else if (ik1 > ik2)
		return 1;
	else
		return 0;
}

uint32 sd_HashFunc_uint64(const void* key)
{
	return (uint32)(*(uint64*)key);
}

int sd_HashKeyCompare_uint64(const void* k1, const void* k2)
{
	uint64 ik1 = *(uint64*)k1;
	uint64 ik2 = *(uint64*)k2;
	if (ik1 < ik2)
		return -1;
	else if (ik1 > ik2)
		return 1;
	else
		return 0;
}
